package com.web.farme.component;

import com.web.farme.annotation.Column;
import com.web.farme.meta.ColumnMetadata;
import com.web.farme.meta.TableFieldInfo;
import com.web.farme.meta.TableInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class DefaultDatabaseHandler implements DatabaseHandler{


    @Override
    public void createTable(Connection connection, TableInfo tableInfo) throws SQLException {
        if (tableInfo.getTableFieldInfos().isEmpty()) {
            return;
        }
        String sql = String.format("create table %s(%s)engine=%s default charset=%s;",
                tableInfo.getTableName(),
                tableInfo.getTableFieldInfos().values().stream().map(v -> this.buildColumnScript(v, false)).collect(Collectors.joining(",")),
                tableInfo.getEngine(),
                tableInfo.getCharset());
        log.info(sql);
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.execute();
        if (tableInfo.getComment() != null && !tableInfo.getComment().equals("")) {
            preparedStatement = connection.prepareStatement(String.format("alter table %s comment '%s';", tableInfo.getTableName(), tableInfo.getComment()));
            preparedStatement.execute();
        }
    }

    @Override
    public void addColumn(Connection connection, TableFieldInfo tableFieldInfo, String tableName) throws SQLException {
        String sql = String.format("alter table %s add %s;",
                tableName,
                this.buildColumnScript(tableFieldInfo, false));
        log.info(sql);
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.execute();
    }

    @Override
    public void modifyColumn(Connection connection, ColumnMetadata columnMetadata, TableFieldInfo tableFieldInfo, String tableName) throws SQLException {
        boolean isModify = false;
        if (columnMetadata.getColumnName() == null) {
            columnMetadata.setColumnName("");
        }
        if (!columnMetadata.getColumnName().toLowerCase().equals(tableFieldInfo.getColumnName().toLowerCase())) {
            isModify = true;
        }
        if (columnMetadata.getColumnType() == null) {
            columnMetadata.setColumnType("");
        }
        if (!columnMetadata.getColumnType().toLowerCase().equals(tableFieldInfo.getColumnType().toLowerCase())) {
            isModify = true;
        }
        if (columnMetadata.getLength() == null) {
            columnMetadata.setLength(-1);
        }
        if (!columnMetadata.getLength().equals(tableFieldInfo.getLength())) {
            isModify = true;
        }
        if (columnMetadata.getIsNull() == null) {
            columnMetadata.setIsNull("");
        }
        if (!columnMetadata.getIsNull().toLowerCase().equals(tableFieldInfo.isNull() ? "yes" : "no")) {
            isModify = true;
        }
        if (columnMetadata.getComment() == null) {
            columnMetadata.setComment("");
        }
        if (tableFieldInfo.getComment() == null) {
            tableFieldInfo.setComment("");
        }
        if (!columnMetadata.getComment().toLowerCase().equals(tableFieldInfo.getComment().toLowerCase())) {
            isModify = true;
        }
        if (columnMetadata.getColumnDefault() == null) {
            columnMetadata.setColumnDefault("");
        }
        if (tableFieldInfo.getColumnDefault() == null) {
            tableFieldInfo.setColumnDefault("");
        }
        if (!columnMetadata.getColumnDefault().toLowerCase().equals("current_timestamp") && !columnMetadata.getColumnDefault().toLowerCase().equals(tableFieldInfo.getColumnDefault().toLowerCase())) {
            isModify = true;
        }
        if (isModify) {
            String sql = String.format("alter table %s modify %s;",
                    tableName,
                    this.buildColumnScript(tableFieldInfo, true));
            log.info(sql);
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.execute();
        }
    }

    @Override
    public boolean tableIsExist(Connection connection, String tableName) throws SQLException {
        PreparedStatement preparedStatement = connection.prepareStatement(
                "select count(*) " +
                        "from information_schema.tables " +
                        "where table_name = ? " +
                        "and table_schema = (select database())");
        preparedStatement.setString(1, tableName);
        ResultSet resultSet = preparedStatement.executeQuery();
        boolean isExist;
        if (resultSet.next()) {
            isExist = resultSet.getInt(1) > 0;
        } else {
            isExist = false;
        }
        return isExist;
    }

    @Override
    public Map<String, ColumnMetadata> getColumnMetaData(Connection connection, String tableName) throws SQLException {
        PreparedStatement preparedStatement = connection.prepareStatement(
                "select * " +
                        "from information_schema.columns " +
                        "where table_name = ? " +
                        "and table_schema = (select database())");
        preparedStatement.setString(1, tableName);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<ColumnMetadata> columnMetadataList = this.resultToObject(resultSet, ColumnMetadata.class);
        if (columnMetadataList == null) {
            return null;
        }
        Map<String, ColumnMetadata> result = new HashMap<>();
        for (ColumnMetadata columnMetadata : columnMetadataList) {
            result.put(columnMetadata.getColumnName().toLowerCase(), columnMetadata);
        }
        return result;
    }

    private <T> List<T> resultToObject(ResultSet resultSet, Class<T> tClass) {
        try {
            List<T> resultList = new ArrayList<>();
            Field[] fields = tClass.getDeclaredFields();
            Map<String, Field> fieldMap = new HashMap<>();
            for (Field field : fields) {
                field.setAccessible(true);
                if (!field.isAnnotationPresent(Column.class)) {
                    continue;
                }
                fieldMap.put(field.getAnnotation(Column.class).columnName(), field);
            }
            TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
            ResultSetMetaData metaData = resultSet.getMetaData();
            while (resultSet.next()) {
                T result = tClass.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    Field field = fieldMap.get(metaData.getColumnName(i));
                    if (field != null) {
                        TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(field.getType(), JdbcType.forCode(metaData.getColumnType(i)));
                        field.set(result, typeHandler.getResult(resultSet, metaData.getColumnName(i)));
                    }
                }
                resultList.add(result);
            }
            return resultList;
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    @Override
    public void handleIndex(Connection connection, TableInfo tableInfo) throws SQLException {

    }

    private String buildColumnScript(TableFieldInfo tableFieldInfo, boolean isModify) {
        StringBuilder result = new StringBuilder(tableFieldInfo.getColumnName()).append(" ").append(tableFieldInfo.getColumnType());
        if (tableFieldInfo.getLength() != -1) {
            result.append("(").append(tableFieldInfo.getLength()).append(")");
        }
        if (tableFieldInfo.isPrimaryKey()) {
            if (!isModify) {
                result.append(" ").append("primary key");
            }
            Column primaryKey = tableFieldInfo.getField().getAnnotation(Column.class);
            if (primaryKey.auto()) {
                result.append(" ").append("auto_increment");
            }
        }
        if (tableFieldInfo.getColumnDefault() != null && !tableFieldInfo.getColumnDefault().equals("")) {
            result.append(" ").append("default '").append(tableFieldInfo.getColumnDefault()).append("'");
        }
        if (tableFieldInfo.isNull()) {
            result.append(" null");
        } else {
            result.append(" not null");
        }
        if (tableFieldInfo.getComment() != null && !tableFieldInfo.getComment().equals("")) {
            result.append(" comment '").append(tableFieldInfo.getComment()).append("'");
        }
        return result.toString();
    }
}
